import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

@SuppressWarnings("restriction")
public class IglooLogger
{
	static String rev = "1.0.6";
	static FileWriter fileWriter;
	static StringBuffer outputBuffer; 
	private static final String iglooFileName= "c:\\igloo.log";
	private static final String noaaUrl = "http://www.weather.gov/xml/current_obs/KHEF.xml";
	private static int noaaPickupPeriodMin = 10;
	private static String noaaLastPickup = "";
	private static final String[] comPorts = {"COM3","COM7"};
	private static DocumentBuilder docBuilder = null;
	private static Runtime runtime = Runtime.getRuntime();
	
    public IglooLogger()
    {
        super();
        try {
			fileWriter = new FileWriter(iglooFileName,true);
			outputBuffer = new StringBuffer();
		} catch (IOException e) {
			e.printStackTrace();
		}
        System.out.println("Logging data to " + iglooFileName);
    }
    
    void connect ( String portName ) throws Exception
    {
        CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
        if ( portIdentifier.isCurrentlyOwned() )
        {
            System.out.println("Error: Port is currently in use");
        }
        else
        {
            CommPort commPort = portIdentifier.open(this.getClass().getName(),2000);
            
            if ( commPort instanceof SerialPort )
            {
                SerialPort serialPort = (SerialPort) commPort;
                serialPort.setSerialPortParams(9600,SerialPort.DATABITS_8,SerialPort.STOPBITS_1,SerialPort.PARITY_NONE);
                
                InputStream in = serialPort.getInputStream();
                OutputStream out = serialPort.getOutputStream();
                               
                // (new Thread(new SerialWriter(out))).start();
                
                serialPort.addEventListener(new SerialReader(in,portName));
                serialPort.notifyOnDataAvailable(true);
            } else {
                System.out.println("Error: Only serial ports are handled by this example.");
            }
        }     
    }
    
    /**
     * Handles the input coming from the serial port. A new line character
     * is treated as the end of a block in this example. 
     */
    public static class SerialReader implements SerialPortEventListener 
    {
        private InputStream in;
        private byte[] portBuffer = new byte[1024];
        private String fileBuffer;
        private String portName;
        private int bufferIdx = 0;
        
        public SerialReader ( InputStream in, String portName ) {
            this.in = in;
            this.portName = portName;
        }
        
        public synchronized void serialEvent(SerialPortEvent serialEvent) {
            int data;
            
            if (serialEvent.getSource().toString().equals("//./"+portName)) {
	            try
	            {
	                boolean crFound = false;
	                while ( ( data = in.read()) > -1 )
	                {
	                	if (data == '@') {
	                		//System.out.println("serialEvent[" + portName + "]: found @, breaking read loop");
							crFound = true;
	                		break;
	                	}
	                	portBuffer[bufferIdx++] = (byte) data;
	                }
	                String portString = new String(portBuffer,0,bufferIdx);
	                if (crFound) {
	                	//System.out.println("serialEvent[" + portName + "]: CR found");
						logData(portName, portString);
						bufferIdx = 0;
	                } else {
	                	//System.out.println("serialEvent[" + portName + "]: buffer underrun");	                	
	                }
	                //System.out.println("buffer=[" + portString + "]");
	            }
	            catch ( IOException e )
	            {
	                e.printStackTrace();
	                System.exit(-1);
	            }         
	        }
        }
    }

    public static class SerialWriter implements Runnable 
    {
        OutputStream out;
        
        public SerialWriter ( OutputStream out )
        {
            this.out = out;
        }
        
        public void run ()
        {
            try
            {                
                int c = 0;
                while ( ( c = System.in.read()) > -1 )
                {
                    this.out.write(c);
                }                
            }
            catch ( IOException e )
            {
                e.printStackTrace();
                System.exit(-1);
            }            
        }
    }
    
    private static void getNoaa() {
    	if (docBuilder==null) {
    		try {
				docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			} catch (ParserConfigurationException e) {
				e.printStackTrace();
			}
    	}
        try {
        	URL url = new URL(noaaUrl);
        	InputStream urlStream = url.openStream();
			Document doc = docBuilder.parse(urlStream);
			String stationId = doc.getElementsByTagName("station_id").item(0).getTextContent();
			noaaPickupPeriodMin = Integer.valueOf((doc.getElementsByTagName("suggested_pickup_period").item(0).getTextContent()));
			String noaaObsTime = doc.getElementsByTagName("observation_time_rfc822").item(0).getTextContent();
			if (! noaaObsTime.equals(noaaLastPickup)) {
				String[] tags = {
						"observation_time_rfc822","latitude","longitude",
						"weather","relative_humidity",
						"wind_dir","wind_degrees","wind_mph",
						"pressure_in","temp_c","dewpoint_c","windchill_c","visibility_mi"};
				for (int i=0; i<tags.length; i++) {
					NodeList e = doc.getElementsByTagName(tags[i]);
					if (e.getLength()==1) {
						String value = doc.getElementsByTagName(tags[i]).item(0).getTextContent();
						logData("NOAA", stationId + "|" + tags[i] + "|" + value);
					} else {
						System.out.println("Required element <" + tags[i] + "> not found in NOAA update");
					}
				}
			} else {
				System.out.println("NOAA update: null");
			}
		} catch (SAXException | IOException e) {
			e.printStackTrace();
		}
    }
    
    public static void logData(String portName, String data) {
		java.util.Date today = new java.util.Date();
		String date = new java.sql.Timestamp(today.getTime()).toString();
		outputBuffer.append(date + '|');
		outputBuffer.append(System.getenv("COMPUTERNAME") + '|');
		outputBuffer.append(portName + '|');
		outputBuffer.append(data);
		outputBuffer.append("\n");
		try {
			fileWriter.write(outputBuffer.toString());
			fileWriter.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
        System.out.print(outputBuffer);
        //showMem();
        outputBuffer.setLength(0);
        //showMem();
    }
    
    private static void showMem() {
        int mb = 1024*1024;
        System.out.println("##### Heap utilization statistics [MB] #####");
        System.out.println("Used Memory:" + (runtime.totalMemory() - runtime.freeMemory()) / mb);
        System.out.println("Free Memory:" + runtime.freeMemory() / mb);
        System.out.println("Total Memory:" + runtime.totalMemory() / mb);
        System.out.println("Max Memory:" + runtime.maxMemory() / mb);
    }
    
    public static void main ( String[] args ) {
    	System.out.println("Starting serial logger");
    	IglooLogger logger = new IglooLogger();
    	int deviceCount = 0;
        for (int i=0; i<comPorts.length; i++) {
        	try {
        		logger.connect(comPorts[i]);
        		System.out.println("Serial port " + comPorts[i] + " is now open");
        		deviceCount++;
        	} catch (Exception e) {
        		System.out.println("Serial port " + comPorts[i] + " is not available");
        	}
        }
        if (deviceCount==0) {
        	System.out.println("No serial ports found; exiting.");
        	return;
        }
        while (true) {
        	getNoaa();
        	try {
				Thread.sleep(noaaPickupPeriodMin * 60 * 1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
        }
    }
}